package cn.itcast.hiss.process.activiti.handler.processinstance;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.itcast.hiss.api.client.HissProcessConstants;
import cn.itcast.hiss.api.client.processdefinition.ProcessViewerModel;
import cn.itcast.hiss.api.client.processdefinition.viewer.*;
import cn.itcast.hiss.cmd.handler.CmdHandler;
import cn.itcast.hiss.common.enums.ActiveNodeTypeEnum;
import cn.itcast.hiss.common.enums.ModelTypeEnum;
import cn.itcast.hiss.common.enums.ProcessNodeStatusEnum;
import cn.itcast.hiss.common.enums.ProcessStatusEnum;
import cn.itcast.hiss.handler.HandlerIdClientEnum;
import cn.itcast.hiss.message.Message;
import cn.itcast.hiss.message.MessageContext;
import cn.itcast.hiss.message.sender.processdefinition.ProcessViewerModelMessage;
import cn.itcast.hiss.process.activiti.mapper.HissProcessFormMapper;
import cn.itcast.hiss.process.activiti.mapper.HissProcessPreLaunchMapper;
import cn.itcast.hiss.process.activiti.mapper.HissSystemUserMapper;
import cn.itcast.hiss.process.activiti.pojo.HissProcessForm;
import cn.itcast.hiss.process.activiti.pojo.HissProcessPreLaunch;
import cn.itcast.hiss.process.activiti.pojo.HissSystemUser;
import cn.itcast.hiss.process.activiti.service.CommentService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.repository.Model;
import org.activiti.engine.task.IdentityLink;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author: miukoo
 * @describe: 获取流程实例 状态图
 * @date: 2022/12/28 10:10
 */
@Slf4j
@Component
public class PI_GetInstanceViewerHandler implements CmdHandler<ProcessViewerModelMessage> {

    @Autowired
    RuntimeService runtimeService;

    @Autowired
    RepositoryService repositoryService;

    @Autowired
    HistoryService historyService;

    @Autowired
    TaskService taskService;

    @Autowired
    CommentService commentService;

    @Autowired
    HissSystemUserMapper hissSystemUserMapper;

    @Autowired
    private HissProcessFormMapper hissProcessFormMapper;
    @Autowired
    private HissProcessPreLaunchMapper hissProcessPreLaunchMapper;

    @Override
    public void invoke(Message params, MessageContext messageContext) {
        ProcessViewerModel processViewerModel = (ProcessViewerModel)params.getPalyload();
        String processInstanceId = processViewerModel.getProcessInstanceId();
        HissProcessPreLaunch hissProcessPreLaunch = hissProcessPreLaunchMapper.selectById(processInstanceId);
        if(hissProcessPreLaunch!=null){
            loadLaunch(processViewerModel,hissProcessPreLaunch);
        }else {
            HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            // ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            InputStream xml = repositoryService.getResourceAsStream(processInstance.getDeploymentId(), HissProcessConstants.PROCESS_RUN_XML);
            if (xml != null) {
                processViewerModel.setContent(IoUtil.readUtf8(xml));
            } else {
                messageContext.addError("msg", "未找到对应的xml文件");
                return;
            }
            // 获取流程的变量信息
            List<HistoricVariableInstance> lists = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId()).list();
            Map<String, Object> variables = new HashMap<>();
            if (lists != null) {
                for (HistoricVariableInstance inst : lists) {
                    variables.put(inst.getVariableName(), inst.getValue());
                }
            }
            // 写基本信息
            processInfo(processViewerModel, processInstance, variables);
            // 解析是否为管理员
            HissSystemUser hissSystemUser = hissSystemUserMapper.selectById(params.getMessageAuth().getCurrentUser().getUserId());
            if (hissSystemUser != null) {
                processViewerModel.getProcessInfo().setAdmin(true);
            }
            // 解析高亮信息
            parseHighLightedData(processViewerModel, processInstance, variables);
            // 加载绑定的表单数据
            setProcessForm(processInstance, processViewerModel, null);
        }
        // 写回数据
        messageContext.addResultAndCount("viewer",processViewerModel);
    }

    private void setProcessForm(HistoricProcessInstance processInstance,ProcessViewerModel processViewerModel,String launchId){
        LambdaQueryWrapper<HissProcessForm> wrapper = Wrappers.lambdaQuery();
        // 管理员，在流程中心查询，因此不用过滤
        if(processInstance!=null&&!processViewerModel.getProcessInfo().getAdmin()){
            wrapper.eq(HissProcessForm::getTenantId,processInstance.getTenantId());
        }
        if(StrUtil.isNotEmpty(launchId)){
            wrapper.eq(HissProcessForm::getLaunchId,launchId);
        }
        if( processInstance!=null ){
            wrapper.eq(HissProcessForm::getProcessInstanceId, processInstance.getId());
        }
        List<HissProcessForm> hissProcessForms = hissProcessFormMapper.selectList(wrapper);
        processViewerModel.setForms(hissProcessForms.stream().map(item-> BeanUtil.copyProperties(item,ProcessFormInfo.class)).collect(Collectors.toList()));
        // 设置流程配置内容
        if(processInstance!=null) {
            List<String> deploymentResourceNames = repositoryService.getDeploymentResourceNames(processInstance.getDeploymentId());
            if (deploymentResourceNames.contains(HissProcessConstants.PROCESS_SHOW_CONFIG_JSON)) {
                InputStream inputStream = repositoryService.getResourceAsStream(processInstance.getDeploymentId(), HissProcessConstants.PROCESS_SHOW_CONFIG_JSON);
                if (inputStream != null) {
                    processViewerModel.setConfigJson(IoUtil.readUtf8(inputStream));
                }
            }
        }
    }

    private void loadLaunch(ProcessViewerModel processViewerModel, HissProcessPreLaunch hissProcessPreLaunch){
        processViewerModel.setComments(new ArrayList<>());
        processViewerModel.setHighlights(new HashMap<>());
        processViewerModel.setActiveTasks(new ArrayList<>());
        Model model = repositoryService.getModel(hissProcessPreLaunch.getModelId());
        List<String> deploymentResourceNames = repositoryService.getDeploymentResourceNames(model.getDeploymentId());
        for (String deploymentResourceName : deploymentResourceNames) {
            if(HissProcessConstants.PROCESS_RUN_XML.equals(deploymentResourceName)){
                InputStream inputStream = repositoryService.getResourceAsStream(model.getDeploymentId(), HissProcessConstants.PROCESS_RUN_XML);
                processViewerModel.setContent(IoUtil.readUtf8(inputStream));
            }
            if(HissProcessConstants.PROCESS_SHOW_CONFIG_JSON.equals(deploymentResourceName)){
                InputStream inputStream = repositoryService.getResourceAsStream(model.getDeploymentId(), HissProcessConstants.PROCESS_SHOW_CONFIG_JSON);
                processViewerModel.setConfigJson(IoUtil.readUtf8(inputStream));
            }
        }
        processViewerModel.setNodes(new ArrayList<>());
        ProcessInfo processInfo = new ProcessInfo();
        processInfo.setStartUserId(hissProcessPreLaunch.getUserId());
        processInfo.setName(hissProcessPreLaunch.getModelName());
        processInfo.setStatus(ProcessStatusEnum.PREPARE);
        processInfo.setAdmin(false);
        processInfo.setTenantId(hissProcessPreLaunch.getTenantId());
        processInfo.setType(ModelTypeEnum.stringToEnum(model.getKey()));
        processInfo.setStartTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(hissProcessPreLaunch.getCreatedTime().getTime()), ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        processViewerModel.setProcessInfo(processInfo);
        // 加载表单
        setProcessForm(null,processViewerModel,hissProcessPreLaunch.getId() );
    }

    private void parseTip(ViewerNodeInfo viewerNodeInfo,HistoricActivityInstance historicActivityInstance,Map<String,ActHiComment> comments){
        String startTime = "";
        if(historicActivityInstance.getStartTime()!=null){
            startTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(historicActivityInstance.getStartTime().getTime()), ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        }
        // 是人工办理的任务：则显示表格信息
        if(historicActivityInstance.getActivityType().equals("userTask")){
            ViewerTipInfo tipInfo = new ViewerTipInfo();
            viewerNodeInfo.setTipTitle("流程审批节点：");
            tipInfo.setUserId(historicActivityInstance.getAssignee());
            tipInfo.setUserName(historicActivityInstance.getAssignee());
            tipInfo.setStartTime(startTime);
            if(viewerNodeInfo.getStatus()==ProcessNodeStatusEnum.COMPLETE){
                String endTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(historicActivityInstance.getEndTime().getTime()), ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                tipInfo.setEndTime(endTime);
                tipInfo.setDuration(formatDuration(historicActivityInstance.getDurationInMillis()));
            }else{
                tipInfo.setDuration(formatDuration(System.currentTimeMillis()-historicActivityInstance.getStartTime().getTime()));
            }
            // 评论
            ActHiComment comment = comments.get(historicActivityInstance.getTaskId());
            if(comment!=null){
                if(StrUtil.isNotEmpty(comment.getMessage())){
                    tipInfo.setOperateType(comment.getMessage());
                }
                tipInfo.setOperateNote(comment.getFullMsg());
            }
            // 删除原因
            String deleteReason = historicActivityInstance.getDeleteReason();
            if(StrUtil.isNotEmpty(deleteReason)){
                tipInfo.setOperateNote(deleteReason);
                viewerNodeInfo.setStatus(ProcessNodeStatusEnum.CANCEL);
            }
            viewerNodeInfo.setTipObject(tipInfo);
        }else{ //否则显示时间信息
            viewerNodeInfo.setTipTitle("开始时间");
            viewerNodeInfo.setTipContent(startTime);
        }
    }

    public void parseHighLightedData(ProcessViewerModel processViewerModel, HistoricProcessInstance processInstance,Map<String, Object> variables){
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstance.getId()).orderByHistoricActivityInstanceStartTime().desc().list();
        // 记录已经办理的流程节点和
        Map<String, List<ViewerNodeInfo>> highlights = new HashMap<>();
        // 记录已经启动了的活动ID
        Set<String> startedActivityIds = new HashSet<>();
        // 记录进行中的活动ID
        Set<String> activeActivityIds = new HashSet<>();
        // 查询所有的评论信息
        List<ActHiComment> processInstanceComments = commentService.selectByProcessInstanceId(processInstance.getId());
        // 记录当前需要处理的任务，
        List<ActiveNodeInfo> activeTask = new ArrayList<>();
        Map<String,ActHiComment> comments = new HashMap<>();
        if(processInstanceComments!=null){
            for (ActHiComment item : processInstanceComments) {
                if(!comments.containsKey(item.getTaskId())) {
                    comments.put(item.getTaskId(), item);
                }
            }
        }

        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        Process mainProcess = bpmnModel.getMainProcess();
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            List<ViewerNodeInfo> list = highlights.getOrDefault(historicActivityInstance.getActivityId(), new ArrayList<ViewerNodeInfo>());
            ViewerNodeInfo viewerNodeInfo = new ViewerNodeInfo();
            viewerNodeInfo.setNodeId(historicActivityInstance.getActivityId());
            viewerNodeInfo.setActivityType(historicActivityInstance.getActivityType());
            viewerNodeInfo.setTaskId(historicActivityInstance.getTaskId());
            viewerNodeInfo.setAssignee(historicActivityInstance.getAssignee());
            if (historicActivityInstance.getEndTime() != null) {
                if(StrUtil.isNotEmpty(historicActivityInstance.getDeleteReason())){
                    activeActivityIds.add(historicActivityInstance.getActivityId());
                }
                viewerNodeInfo.setStatus(ProcessNodeStatusEnum.COMPLETE);
                list.add(viewerNodeInfo);
            } else {
                viewerNodeInfo.setStatus(ProcessNodeStatusEnum.ACTIVE);
                list.add(0,viewerNodeInfo);
                activeActivityIds.add(historicActivityInstance.getActivityId());
                addActiveTask(bpmnModel,activeTask,historicActivityInstance,variables);
            }
            parseTip(viewerNodeInfo,historicActivityInstance,comments);
            // TODO CANCEL状态后续补充
            highlights.put(historicActivityInstance.getActivityId(),list);
            startedActivityIds.add(historicActivityInstance.getActivityId());
        }
        // 写激活节点信息
        processViewerModel.setActiveTasks(activeTask);
        // 解析高亮线
        parseHighLightedLine(mainProcess,processInstance,highlights,historicActivityInstances,startedActivityIds);
        // 删除进行中活动之后的线和节点
        deleteHighLighted(mainProcess,highlights,activeActivityIds);
        // 设置评论信息
        processViewerModel.setComments(processInstanceComments);
        // 设置节点信息
        processViewerModel.setNodes(parseGraphicList(bpmnModel.getMainProcess(),highlights));
        // 写回高亮信息
        processViewerModel.setHighlights(highlights);
    }

    /**
     * 解析每个图节点的信息
     * @param process
     * @return
     */
    public List<GraphicNodeInfo> parseGraphicList(Process process,Map<String, List<ViewerNodeInfo>> map){
        List<GraphicNodeInfo> list = new ArrayList<>();
        for (FlowElement flowElement : process.getFlowElements()) {
            GraphicNodeInfo info = new GraphicNodeInfo();
            info.setId(flowElement.getId());
            info.setName(flowElement.getName());
            info.setActivityType(flowElement.getClass().getSimpleName());
            if(flowElement instanceof UserTask){
                UserTask userTask = (UserTask) flowElement;
                info.setAssignee(userTask.getAssignee());
            }
            List<ViewerNodeInfo> vs = map.get(flowElement.getId());
            if(vs!=null&&vs.size()>0){
                ViewerNodeInfo viewerNodeInfo = vs.get(0);
                info.setStatus(viewerNodeInfo.getStatus());
                info.setTaskId(viewerNodeInfo.getTaskId());
            }
            list.add(info);
        }
        return list;
    }

    private void addActiveTask(BpmnModel bpmnModel,List<ActiveNodeInfo> activeTask, HistoricActivityInstance historicActivityInstance,Map<String, Object> variables) {
        ActiveNodeInfo activeNodeInfo = new ActiveNodeInfo();
        activeNodeInfo.setActivityType(historicActivityInstance.getActivityType());
        activeNodeInfo.setAssignee(historicActivityInstance.getAssignee());
        activeNodeInfo.setTaskId(historicActivityInstance.getTaskId());
        activeNodeInfo.setNodeId(historicActivityInstance.getActivityId());
        activeNodeInfo.setNodeType(parseMultiInstance(bpmnModel,historicActivityInstance.getActivityId(),variables));
        parseTaskCandidate(activeNodeInfo);
        activeTask.add(activeNodeInfo);
    }

    /**
     * 获取候选人
     * @param activeNodeInfo
     */
    private void parseTaskCandidate(ActiveNodeInfo activeNodeInfo){
        if(StrUtil.isNotEmpty(activeNodeInfo.getTaskId())) {
            try {
                List<IdentityLink> list = taskService.getIdentityLinksForTask(activeNodeInfo.getTaskId());
                activeNodeInfo.setCandidateUser(list.stream().filter(item->item.getType().equals("candidate")).collect(Collectors.toMap(IdentityLink::getUserId, IdentityLink::getType, (oldValue, newValue) -> newValue)));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    private ActiveNodeTypeEnum parseMultiInstance(BpmnModel bpmnModel, String activityId, Map<String,Object> variables){
        FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(activityId);
        Object behavior = flowNode.getBehavior();
        if(behavior instanceof ParallelMultiInstanceBehavior){
            return ActiveNodeTypeEnum.MULTIPLE_PARALLEL;
        }
        if(behavior instanceof SequentialMultiInstanceBehavior){
            return ActiveNodeTypeEnum.MULTIPLE_SEQUENTIAL;
        }
        // 知会和抄送节点判断
        if(variables.containsKey(HissProcessConstants.NODE_TYPE)){
            String typeName = (String) variables.get(HissProcessConstants.NODE_TYPE);
            if(StrUtil.isNotEmpty(typeName)){
                return ActiveNodeTypeEnum.valueOf(typeName);
            }
        }
        // 多人、开始、单人节点的识别
        if(flowNode instanceof UserTask){
            UserTask userTask = (UserTask) flowNode;
            String nodeType = userTask.getAttributeValue(BpmnXMLConstants.ACTIVITI_EXTENSIONS_NAMESPACE, HissProcessConstants.NODE_TYPE);
            if(StrUtil.isNotEmpty(nodeType)){
                return ActiveNodeTypeEnum.valueOf(nodeType);
            }
            return ActiveNodeTypeEnum.SINGLE_APPROVE;
        }
        return ActiveNodeTypeEnum.NONE;
    }

    private void deleteHighLighted(Process mainProcess,Map<String, List<ViewerNodeInfo>> highlights, Set<String> activeActivityIds) {
        for (String activeActivityId : activeActivityIds) {
            FlowNode flowNode = (FlowNode)mainProcess.getFlowElement(activeActivityId);
            deleteHighLightedNode(flowNode,highlights);
        }
    }

    private void deleteHighLightedNode(FlowNode flowNode, Map<String, List<ViewerNodeInfo>> highlights) {
        if(flowNode!=null){
            List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();
            if(outgoingFlows!=null){
                for (SequenceFlow outgoingFlow : outgoingFlows) {
                    highlights.remove(outgoingFlow.getId());// 删除线
                    highlights.remove(outgoingFlow.getTargetRef());// 删除节点
                    // 递归删除下一个节点
                    deleteHighLightedNode((FlowNode) outgoingFlow.getTargetFlowElement(),highlights);
                }
            }
        }
    }

    /**
     * 高亮线解析
     * @param processInstance   流程实例
     * @param highlights        高亮集合
     * @param historicActivityInstances     已执行过的活动列表
     * @param startedActivityIds            已启动过的活动Id集合
     */
    private void parseHighLightedLine(Process mainProcess,HistoricProcessInstance processInstance,Map<String, List<ViewerNodeInfo>> highlights,List<HistoricActivityInstance> historicActivityInstances, Set<String> startedActivityIds){
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            // 如果是完成的节点才判断线
            if(historicActivityInstance.getEndTime()!=null){
                // 获取到xml文件中的节点
                FlowNode flowNode = (FlowNode) mainProcess.getFlowElement(historicActivityInstance.getActivityId());
                List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();// 获取出度集合
                if(outgoingFlows!=null){
                    for (SequenceFlow outgoingFlow : outgoingFlows) {
                        if(startedActivityIds.contains(outgoingFlow.getTargetRef())){// 如果指向顶点开始过，则需要高亮线
                            ViewerNodeInfo viewerNodeInfo = new ViewerNodeInfo();
                            viewerNodeInfo.setNodeId(outgoingFlow.getId());
                            viewerNodeInfo.setStatus(ProcessNodeStatusEnum.COMPLETE);
                            List<ViewerNodeInfo> list = highlights.getOrDefault(outgoingFlow.getId(), new ArrayList<ViewerNodeInfo>());
                            list.add(viewerNodeInfo);
                            highlights.put(outgoingFlow.getId(),list);
                        }
                    }
                }
            }
        }
    }

    private void processInfo(ProcessViewerModel processViewerModel, HistoricProcessInstance historicProcessInstance,Map<String, Object> variables){
        if(historicProcessInstance!=null) {
            ProcessInfo processInfo = new ProcessInfo();
            processInfo.setName(historicProcessInstance.getName());
            processInfo.setType(ModelTypeEnum.DEV);// 流程图类型，目前固定为DEV，表示开发者流程模式
            processInfo.setAdmin(false);// 是否是超管，默认不是
            processInfo.setStartUserId(historicProcessInstance.getStartUserId());// 流程实例的启动人id
            processInfo.setEndReason(historicProcessInstance.getDeleteReason());// 流程实例结束原因
            processInfo.setTenantId(historicProcessInstance.getTenantId());// 商户
            if(historicProcessInstance.getStartTime()!=null){// 激活状态
                processInfo.setStatus(ProcessStatusEnum.ACTIVE);
                processInfo.setDuration(formatDuration(System.currentTimeMillis()-historicProcessInstance.getStartTime().getTime()));
                processInfo.setStartTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(historicProcessInstance.getStartTime().getTime()), ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            }
            if(historicProcessInstance.getEndTime()!=null){// 有结束时间，则流程实例结束
                processInfo.setStatus(ProcessStatusEnum.COMPLETE);
                processInfo.setDuration(formatDuration(historicProcessInstance.getDurationInMillis()));
                processInfo.setEndTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(historicProcessInstance.getEndTime().getTime()), ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            }
            // TODO 剩余PREPARE("预备中"),PENDING("挂起中"),CANCEL("已取消")，在后续进行补充
            String status = (String) variables.get(HissProcessConstants.PROCESS_STATUS);
            if(ProcessStatusEnum.CANCEL.name().equals(status)){
                processInfo.setStatus(ProcessStatusEnum.CANCEL);
            }
            if(ProcessStatusEnum.REJECT.name().equals(status)){
                processInfo.setStatus(ProcessStatusEnum.REJECT);
            }
            processViewerModel.setProcessInfo(processInfo);
        }
    }

    public static String formatDuration(long durationInMillis) {
        long days = TimeUnit.MILLISECONDS.toDays(durationInMillis);
        long hours = TimeUnit.MILLISECONDS.toHours(durationInMillis) % 24;
        long minutes = TimeUnit.MILLISECONDS.toMinutes(durationInMillis) % 60;
        long seconds = TimeUnit.MILLISECONDS.toSeconds(durationInMillis) % 60;
        long milliseconds = durationInMillis % 1000;

        StringBuilder sb = new StringBuilder();
        if (days > 0) {
            sb.append(days).append("天 ");
        }
        if (hours > 0) {
            sb.append(hours).append("小时 ");
        }
        if (minutes > 0) {
            sb.append(minutes).append("分钟 ");
        }
        if (seconds > 0) {
            sb.append(seconds).append("秒 ");
        }
        if (milliseconds > 0) {
            sb.append(milliseconds).append("毫秒 ");
        }

        return sb.toString().trim();
    }

    @Override
    public String getId() {
        return HandlerIdClientEnum.PI_GET_INSTANCE_DEV_VIEWER.getId();
    }
}
